﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved All Rights Reserved All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Diagnostics;

namespace FinPlusUtility
{
    public static class Utility
    {
        public class Config : Dictionary<string, Dictionary<string, string>> { }
        public enum Env { dev, test, uat, prod, unknown }

        //configs and settings
        public static Config ConfigXml(string path, out Env environment)
        {
            var doc = new XmlDocument();

            doc.Load(path);
            var res = new Config();

            var env = doc.SelectSingleNode("configuration/environment").Attributes["name"].Value;
            var config = doc.SelectNodes("configuration/" + env);
            if (!Enum.TryParse(env, out environment))
                throw new FormatException("!config environment " + env + " invalid should be dev, uat or prod");

            foreach (XmlElement settings in config)
            {
                foreach (XmlElement setting in settings)
                {
                    var d = res[setting.Name] = new Dictionary<string, string>();
                    foreach (XmlElement value in setting)
                        d[value.Attributes["name"].Value] = value.Attributes["value"].Value;
                }
            }
           
            return res;
        }

        public static Config UserSettingsRegistry(string path, string name)
        {
            //TODO
            return new Config();
        }

        public static Config UserSettingsDatabase(string path, string name)
        {
            //TODO
            return new Config();
        }

        public static string XmlTryParse(XmlElement element, string name, string defaultValue = "")
        {
            XmlNode n = element.Attributes[name];
            if (n == null) return defaultValue;
            return n.Value;
        }

        public static XmlAttribute XmlAttribute(XmlDocument doc, string name, string value)
        {
            var attribute = doc.CreateAttribute(name);
            attribute.Value = value;

            return attribute;
        }

        //asssembly thread
        public static Assembly GetReferencedAssembly(Assembly executimgAssembly, string name)
        {
            var n = name.ToLower();
            foreach (var s in executimgAssembly.GetReferencedAssemblies())
                if (s.Name.ToLower() == n)
                    return Assembly.Load(s);

            return null;
        }

        public static string GetAssemblyPath(Assembly assembly)
        {
            return assembly.Location.Replace(assembly.ManifestModule.Name, string.Empty).ToLower();
        }

        public static Assembly[] GetAssemblys(string path, string[] names)
        {
            var res = new List<Assembly>();
            foreach (var name in names)
                res.Add(GetAssembly(path, name));

            return res.ToArray();
        }

        public static Assembly GetAssembly(string path, string name)
        {
            try
            {
                return Assembly.LoadFile(string.Format("{0}{1}.dll", path, name));
            }
            catch
            {
                return null;
            }
        }

        public static string GetConfigPath(Assembly assembly)
        {
            return assembly.Location.ToLower().Replace(".exe", ".config.xml").Replace(".dll", ".config.xml");
        }

        public static string ThreadId()
        {
            return Thread.CurrentThread.ManagedThreadId.ToString();
        }

        public static string[,] AssemblyInfo(Assembly assembly)
        {
            var f = FileVersionInfo.GetVersionInfo(assembly.Location);
            string[,] info = {{"Version", f.ProductVersion}, 
                               {"Company", f.CompanyName},
                               {"Decsription", f.FileDescription},
                               {"Copyright", f.LegalCopyright}};
            return info;
        }

        //casts type checks
        public static bool IsTypeOf<TInterface>(this Type type)
        {
            var interfaceType = typeof(TInterface);

            if (interfaceType.Name.Equals(type.Name.Split('`')[0]))
                return true;

            return interfaceType.IsAssignableFrom(type);
        }

        public static bool TryCast<T>(this object obj, T defaultValue, out T result)
        {
            try
            {
                result = (T)obj;
                return true;
            }
            catch
            {
                result = defaultValue;
                return false;
            }
        }

        //enum parsing
        public static T EnumParse<T>(this string stringValue, T defaultValue)
        {
            if (!Enum.IsDefined(typeof(T), stringValue))
                return defaultValue;

            return (T)Enum.Parse(typeof(T), stringValue);
        }

        public static T EnumParse<T>(this string stringValue)
        {
            if (!Enum.IsDefined(typeof(T), stringValue))
                throw new InvalidOperationException(string.Format("{} not defined", stringValue));

            return (T)Enum.Parse(typeof(T), stringValue);
        }

        //array and collection parsing
        public static IDictionary<string, object> Array2DToDict(object[,] array)
        {
            var res = new Dictionary<string, object>();

            if (array == null)
                return null;

            for (var i = 0; i <= array.GetUpperBound(0); i++ )
                res[array[i, 0].ToString()] = array[i, 1];

            return res;
        }

        public static string StringFromArray1d(object[] array, char delimiter = '%', string brackets = null)
        {
            if (array == null)
                return "";

            var str = string.Join(delimiter.ToString(), array);

            if (brackets != null)
                str = string.Format("{0}{1}{2}", brackets.First(), str, brackets.Last());

            return str;
        }

        public static string StringFromArray2d(object[,] array, char rowDelimiter = '%', char colDelimiter = '$')
        {
            try
            {
                //TODO clean all str parsing use string builder reg ex etc

                if (array == null) return "";

                StringBuilder sb = new StringBuilder();
                int rows = array.GetUpperBound(0);
                int cols = array.GetUpperBound(1);

                for (int i = 0; i <= rows; i++)
                {
                    for (int j = 0; j <= cols; j++)
                    {
                        sb.Append(array[i, j]);
                        if (i == rows && j == cols) continue;
                        sb.Append(j == cols ? rowDelimiter : colDelimiter);
                    }
                }
                return sb.ToString();
            }
            catch (Exception e)
            {
                return e.ToString();
            }
        }

        public static object[] StringToArray1d(string str, char delimiter = '%', string brackets = null, string remove = @"\t|\n|\r| ", string replace = "")
        {
            if (string.IsNullOrEmpty(str))
                return null;

            if (brackets != null)
                str = Replace(str, remove, replace).Trim(brackets.ToCharArray());

            var array = str.Split(new char[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
            return array;
        }

        public static object StringToArray2d(string str, char delimiter, string brackets, string remove = @"\t|\n|\r| ", string replace = "")
        {
            try
            {
                var  b = brackets.ToCharArray();
                if (b.Length != 2)
                    throw new FormatException("incorect bracket format should be 2 char string");

                var str0 = Replace(str, remove, replace);
                var str1 = str0.Replace(string.Format("{0}{1}{2}", b[1], delimiter, b[0]), ";");
                var str2 = str1.Replace(b[0].ToString(), string.Empty).Replace(b[1].ToString(), string.Empty);

                return StringToArray2d(str2, '¬', delimiter);
            }
            catch (Exception e)
            {
                return new string[,] { { "Error" }, { e.Message } };
            }
        }
        
        public static object StringToArray2d(string str, char rowDelimiter ='%', char colDelimiter = '$')
        {
            try
            {
                string[] rows = str.Split(rowDelimiter);
                string[,] array = null; 

                for (int i = 0; i < rows.GetLength(0); i++)
                {
                    string[] cols = rows[i].Split(colDelimiter);
                    if (array == null)
                        array = new string[rows.GetLength(0), cols.GetLength(0)];

                    for (int j = 0; j < cols.GetLength(0); j++)
                        array[i, j] = cols[j];
                }
                return array;
            }
            catch (Exception e)
            {
                return new string[,] { { "Error" }, { e.Message } };
            }
        }

        public static string[][] ToJaggedArray(string str, char rowDelimiter = '\n', char colDelimiter = ',')
        {
            try
            {
                return str.Split(rowDelimiter).Select(i => i.Split(colDelimiter).ToArray()).ToArray();
            }
            catch 
            {
                return null;
            }
        }

        public static string[] Split(string str, char delimiter=',', string remove = @"\t|\n|\r| ", string replace = "")
        {
            return Replace(str, remove, replace).Split(delimiter);
        }

        public static IDictionary<string, string> StringToDict(string str, bool isKeyFirst = true, char delimiter1 = ';', char delimiter2 = ':', string remove = @"\t|\n|\r| ", string replace = "")
        {
            try
            {
                if (string.IsNullOrEmpty(str) || str.Equals("NA"))
                    return new Dictionary<string, string>();

                if (isKeyFirst)
                    return Split(str.TrimEnd(delimiter1), delimiter1, remove, replace).ToDictionary(f => f.Split(delimiter2)[0], f => f.Split(delimiter2)[1]);

                return Split(str, delimiter1, remove, replace).ToDictionary(f => f.Split(delimiter2)[1], f => f.Split(delimiter2)[0]);
            }
            catch
            {
                return null;
            }
        }

        public static IDictionary<string, string> StringToDict(string str, char delimiter = '%', string brackets = "{}", string remove = @"\t|\n|\r| ", string replace = "")
        {
            try
            {
                if (string.IsNullOrEmpty(str))
                    return null;

                if(!string.IsNullOrEmpty(remove))
                    str = Replace(str, remove, replace);

                var b = brackets.ToCharArray();
                if (b.Length != 2)
                    throw new FormatException("incorect bracket format should be 2 char string");

                var res = new Dictionary<string, string>();
                var array = str.Replace(string.Format("{0}{1}{2}", b[1], delimiter, b[0]), "¬").Split('¬');

                foreach (var val in array)
                {
                    var kvp = val.Split(delimiter);
                    res.Add(kvp[0].Trim(b), kvp[1].Trim(b));
                }

                return res;
            }
            catch (Exception e)
            {
                throw new FormatException(string.Format("unable to parse {0}", str), e);
            }
        }

        public static string StringFromDictOfArrays(IDictionary<string, string[]> dict, char delimiter = '%', string brackets = "{}")
        {
            var b = brackets.ToCharArray();
            if (b.Length != 2)
                throw new FormatException("incorrect bracket format should be 2 char string");

            var res = string.Join(delimiter.ToString(), dict.Select(k=> string.Format("{0}{1}{2}{3}{4}{5}{6}", b[0], 
                k.Key.ToString(), delimiter, b[0], string.Join(delimiter.ToString(), k.Value), b[1], b[1])).ToArray());

            return string.Format("{0}{1}{2}", b[0], res, b[1]);
        }

        public static IDictionary<string, string[]> StringToDictOfArrays(string str, char delimiter = '%', string brackets = "{}", string remove = @"\t|\n|\r| ", string replace = "")
        {
            try
            {
                if (string.IsNullOrEmpty(str) || str.Equals(brackets))
                    return null;

                if (!string.IsNullOrEmpty(remove))
                    str = Replace(str, remove, replace);

                var b = brackets.ToCharArray();
                if (b.Length != 2)
                    throw new FormatException("incorrect bracket format should be 2 char string");

                var res = new Dictionary<string, string[]>();
                var array = str.Replace(string.Format("{0}{1}{2}", b[1],delimiter, b[0]), "¬").Split('¬');

                foreach(var val in array)
                {
                    var kvp = val.Replace(string.Format("{0}{1}", delimiter, b[0]),"¬").Split('¬');
                    res.Add(kvp[0].Trim(b), kvp[1].Trim(b).Split(delimiter));
                }

                return res;
            }
            catch(Exception e)
            {
                throw new FormatException(string.Format("unable to parse {0}", str),e);
            }
        }

        public static IList<string[]> StringToListOfArrays(string str, char delimiter = '%', string brackets = "{}", string remove = @"\t|\n|\r| ", string replace = "")
        {
            try
            {
                if (string.IsNullOrEmpty(str))
                    return null;

                if (!string.IsNullOrEmpty(remove))
                    str = Replace(str, remove, replace);

                var b = brackets.ToCharArray();
                if (b.Length != 2)
                    throw new FormatException("incorect bracket format should be 2 char string");

                var res = new List<string[]>();
                var array = str.Trim(b).Replace(string.Format("{0}{1}{2}", b[1], delimiter, b[0]), "¬").Split('¬');

                foreach (var val in array)
                {
                    var value = val.Split(delimiter);
                    res.Add(value);
                }

                return res;
            }
            catch (Exception e)
            {
                throw new FormatException(string.Format("unable to parse {0}", str), e);
            }
        }

        public static object Array1dToArray2d(object[] array)
        {
            var r = new object[1, array.Length];

            var i = 0;
            foreach (var o in array)
            {
                r[0,i] = array[i];
                i++;
            }
            return r;
        }

        public static object ArrayTranspose(object[] array)
        {
            var r = new object[array.Length, 1];

            var i = 0;
            foreach (var o in array)
            {
                r[i, 0] = array[i];
                i++;
            }
            return r;
        }

        public static object StringWithInfoToObject(string str, string InfoType, char delimiterInfo = '|', char delimiterRow = '%', char delimiterCol = '$')
        {
            var s = str.Split(delimiterInfo);
            var val = s[s.GetUpperBound(0)];

            switch (InfoType)
            {
                case "String": return val;
                case "Number": return double.Parse(val);
                case "Array1D": return StringToArray1d(val);
                case "Array2D": return StringToArray2d(val);
            }
            return "#info type not recognised";
        }

        //replace
        public static string ReplaceFirst(string source, string find, string replace)
        {
            int Place = source.IndexOf(find);
            string result = source.Remove(Place, find.Length).Insert(Place, replace);
            return result;
        }

        public static string ReplaceLast(string source, string find, string replace)
        {
            int Place = source.LastIndexOf(find);
            string result = source.Remove(Place, find.Length).Insert(Place, replace);
            return result;
        }

        public static string Replace(string value, string remove = @"\t|\n|\r| ", string replace = "")
        {
            return Regex.Replace(value, remove, replace);
        }

        public static string Replace(string value, IDictionary<string, string> replace)
        {
            if (replace != null && value.StartsWith("[") && value.EndsWith("]") && replace.ContainsKey(value))
                value = replace[value];
            else if (replace != null && replace.Count > 0)
                foreach (var val in replace)
                    if (val.Key.StartsWith("[") && val.Key.EndsWith("]") && value.Contains(val.Key))
                        value = ReplaceLast(value, val.Key, val.Value);
                        
            return value;
        }

        public static string RemoveNumeric(string value)
        {
            return Regex.Replace(value, "[0-9]", "");
        }

        public static string RemoveAlpha(string value)
        {
            return Regex.Replace(value, "[^.0-9]", "");
        }

        //date time parse
        public static DateTime NextWorkDay(DateTime date, int add)
        {
            var resDate = date;
            int found = 0;
            int step = add > 0 ? 1 : -1;
            while (Math.Abs(found) <  Math.Abs(add))
            {
                resDate = resDate.AddDays(step);
                if (resDate.DayOfWeek != DayOfWeek.Sunday && resDate.DayOfWeek != DayOfWeek.Saturday)
                    found++;
            }
            return resDate;
        }

        public static DateTime ParseDateTime(string dateTime)
        {
            DateTime res1 = DateTime.MaxValue;
            if (dateTime.Equals("Today") || dateTime.Equals("T"))
                return DateTime.Now.Date;

            if (dateTime.Equals("Yesterday") || dateTime.Equals("T-1"))
                return NextWorkDay(DateTime.Now.Date, -1);

            if (DateTime.TryParse(dateTime, out res1))
                return res1;

            double res2;
            if (double.TryParse(dateTime, out res2))
                return DateTime.FromOADate(res2);

            return res1;
        }

        public static string FormatDateTime(string value, string dateFormat = "dd-MMM-yyyy", string dateFomatLong = "dd-MMM-yyyy  HH:mm:ss")
        {
            if (value.ToString().Equals("Unbooked"))
                return value;

            var dt = DateTime.FromOADate(double.Parse(value));

            if (dt.TimeOfDay.TotalSeconds == 0)
                return dt.ToString(dateFormat);

            return dt.ToString(dateFomatLong);
        }

        //other parse
        public static double DoubleParse(string value, double defaultValue = 0)
        {
            if (value == null) return defaultValue;

            double.TryParse(value, out defaultValue);

            return defaultValue;
        }

        public static int IntParse(string value, int defaultValue = 0)
        {
            if (value == null) return defaultValue;

            int.TryParse(value, out defaultValue);
            
            return defaultValue;
        }

        //other
        public static string Guid()
        {
            return System.Guid.NewGuid().ToString().Replace("$","£").Replace("%","^");
        }

        public static int GetNumberOfDp(double value)
        {
            var res = value.ToString().Split('.');
            if (res.Count() < 2)
                return 0;

            return res[1].Length;
        }
    }
}
